package com.demo.redis.redis.aop;

import com.demo.redis.redis.annotation.RedisLock;
import com.demo.redis.redis.annotation.RedisLocks;
import jakarta.annotation.Resource;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.context.annotation.Configuration;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * RedisLock aop拦截器
 * 通过拦截方法，获取入参的值生成redis锁。该拦截器支持直接获取参数值，对象属性值，集合里的对象属性值
 * @author cyj
 **/
@Slf4j
@Aspect
@Configuration
public class RedisLockAop {

    @Resource
    private RedissonClient redissonClient;

    @Pointcut("@annotation(com.demo.redis.redis.annotation.RedisLock) || @annotation(com.demo.redis.redis.annotation.RedisLocks)")
    public void addAdvice() {}

    @Around("addAdvice()")
    public Object interceptor(ProceedingJoinPoint pjp) throws Throwable {
        Method method = AopUtil.getMethod(pjp);
        List<RedisLock> redisLocks = getRedisLocks(method);
        // 获取方法入参的值
        List<ReidsLockBo> list = genReidsLockBo(pjp, redisLocks);
        // 如果注解设置不等待锁，直接抛异常
        for (ReidsLockBo bo : list) {
            if (!bo.getRedisLock().isWaitForLock() && bo.getLock().isLocked()) {
                String msg = String.format("缓存key:%s 已上锁", bo.getKey());
                log.error(msg);
                throw new RuntimeException(msg);
            }
        }

        for (ReidsLockBo bo : list) {
            if (bo.getRedisLock().expire() > 0) {
                bo.getLock().lock(bo.getRedisLock().expire(), TimeUnit.MILLISECONDS);
            } else {
                log.info("缓存key：{} 加锁", bo.getKey());
                bo.getLock().lock();
            }
        }

        try {
            return pjp.proceed();
        } finally {
            for (ReidsLockBo bo : list) {
                log.info("缓存key：{} 释放", bo.getKey());
                bo.getLock().unlock();
            }
        }
    }

    private List<ReidsLockBo> genReidsLockBo(ProceedingJoinPoint pjp, List<RedisLock> redisLocks) {
        return redisLocks.stream().map(s -> {
            Object key = genKey(pjp, s);
            ReidsLockBo bo = new ReidsLockBo();
            bo.setKey(key);
            bo.setRedisLock(s);
            bo.setLock(getLock(key, s));
            return bo;
        }).collect(Collectors.toList());
    }

    private List<RedisLock> getRedisLocks(Method method) {
        RedisLocks redisLock = method.getAnnotation(RedisLocks.class);
        if (redisLock != null) {
            return List.of(redisLock.value());
        }
        return List.of(method.getAnnotation(RedisLock.class));
    }

    private Object genKey(ProceedingJoinPoint pjp, RedisLock redisLock) {
        if (StringUtils.isAllBlank(redisLock.key(), redisLock.constantKey())) {
            throw new RuntimeException("key和constantKey不能同时为空");
        }
        if (StringUtils.isNoneBlank(redisLock.key(), redisLock.constantKey())) {
            throw new RuntimeException("key和constantKey不能同时有值");
        }

        // 获取固定值
        if (StringUtils.isNotBlank(redisLock.constantKey())) {
            return redisLock.constantKey();
        }

        String keyEl = redisLock.key();

        // 解析表达式,生成最终的key
        Object key = AopUtil.springElParser(keyEl, pjp);
        log.info("SpringEL表达式解析后的key:{}", key.toString());
        return key;
    }

    private RLock getLock(Object key, RedisLock redisLock) {
        // 如果key是集合，就获取MultiLock，每个key对应的锁使用公平锁
        if (key instanceof Collection) {
            Collection<?> keys = (Collection<?>) key;
            List<RLock> locks = new ArrayList<>();
            for (Object o : keys) {
                RLock lock = genLock(o, redisLock, true);
                locks.add(lock);
            }
            return redissonClient.getMultiLock(locks.toArray(new RLock[]{}));
        } else {
            return genLock(key, redisLock, false);
        }
    }

    private RLock genLock(Object key, RedisLock redisLock, boolean isFairLock) {
        String redisKey;
        if (StringUtils.isNotBlank(redisLock.prefix())) {
            redisKey = redisLock.prefix() + ":" + key;
        } else {
            redisKey = String.valueOf(key);
        }
        log.info("缓存key:{}", redisKey);
        if (isFairLock) {
            return redissonClient.getFairLock(redisKey);
        }
        return redissonClient.getLock(redisKey);
    }

    @Data
    private class ReidsLockBo {
        private Object key;
        private RedisLock redisLock;
        private RLock lock;
    }
}
